/* assemble.c
 *
 * assemble the CASL source program
 *
 * written by taiqwu<taiqwu@yeah.net> on March 6, 2002
 */

#include <stdio.h>
#include <ctype.h>

#include "casl.h"

#include "os.h"

#include "assemble.h"
#include "instructions.h"

/* memory.c */
void CodeRead (UWORD address, CODE * code);
void CodeWrite (UWORD address, CODE * code);
int memoryWrite (UWORD address, WORD value);
WORD memoryRead (UWORD address);
void memoryClear (void);

extern int statusAssembled;

LABEL *labels = NULL;
VARIABLE *vars = NULL;
SENTENCE *sentences = NULL;
int SentenceNum = 0;
static int CurrentLine = 0;

static char *strGR[5] = { "GR0", "GR1", "GR2", "GR3", "GR4" };	/* strings of GR */

/* display message of warning or error
 *
 * input(parameter):
 *   message - message of warning or error
 *   i - which line in source program
 */
typedef enum
{ WARNING_MESSAGE, ERROR_MESSAGE }
MESSAGE_TYPE;

static void
DisplayWarningError (MESSAGE_TYPE type, char *message, ...)
{
  int buffer_size = 1024;
  char buffer[buffer_size];
  va_list ap;

  va_start (ap, message);
  vsnprintf (buffer, buffer_size, message, ap);
  va_end (ap);

  switch (type)
    {
    case WARNING_MESSAGE:
      display (_("Warning: %s in line %d: %s\n"), buffer, CurrentLine + 1,
	       sentences[CurrentLine].string);
      break;
    case ERROR_MESSAGE:
      display (_("Error: %s in line %d: %s\n"), buffer, CurrentLine + 1,
	       sentences[CurrentLine].string);
      break;
    }
}

static void
DisplayWarningErrorWithLength (MESSAGE_TYPE type, char *message, char *string,
			       int length)
{
  char ch;

  ch = string[length];
  string[length] = '\0';
  DisplayWarningError (type, message, string);
  string[length] = ch;
}

/* define if not GNU extension */
static int
isblank (char c)
{
  return c == ' ' || c == '\t';
}

/* skip blank
 *
 * parameter:
 *   string - skip the blank at the begin of the string
 * return:
 *   the number of blank character
 */
static int
SkipBlank (const char *string)
{
  int i = 0;

  while (string[i] && isblank (string[i]))
    i++;
  return i;
}

/* skip a word with alphabet and digit
 *
 * parameter:
 *   string - skip the alphabet & digit at the begin of the string
 * return:
 *   the number of alphabet & digit
 */
static int
SkipWord (const char *string)
{
  int i = 0;

  while (string[i] && isalnum (string[i]))
    i++;
  return i;
}

/* skip a comma which must exist. if not exist comma, display a error message
 *
 * parameter:
 *   string - blanks + , + blanks
 * return:
 *   length of blanks + , + blanks
 */
static int
SkipComma (const char *string)
{
  int i = 0;

  i = SkipBlank (string);
  if (string[i] && string[i] != ';')	/* not at the end of sentence */
    {
      if (string[i] == ',')
	{
	  i++;
	  i += SkipBlank (string + i);
	}
      else
	DisplayWarningError (ERROR_MESSAGE, _("comma missing in \"%s\""),
			     string);
    }

  return i;
}

/* add a label to the top of the label list
 *
 * parameter:
 *   string - the begin of a label string
 *   address - the address of label
 * return:
 *   the length of label string */
static int
AddLabel (const char *string, UWORD address)	/* add a label at the head of labels link */
{
  LABEL *alabel;
  int j;

  j = SkipWord (string);
  if ((alabel = (LABEL *) malloc (sizeof (LABEL))) != NULL)
    if ((alabel->name = (char *) malloc (sizeof (char) * (j + 1))) != NULL)
      {
	memcpy (alabel->name, string, j);
	alabel->name[j] = '\0';
	alabel->address = address;
	alabel->next = labels;
	labels = alabel;
      }
    else
      {
	free (alabel);
	/* Show error: no enough memory */
	display (_("No enough memory"));
      }
  else				/* Show error: no enough memory */
    {
      display (_("No enough memory"));
    }
  return j;
}

/* find a label
 *
 * parameter:
 *   string - the begin of label string
 * return:
 *   the pointer of the label, if found
 *   NULL, if no found
 */
static LABEL *
FindLabel (char string[])
{
  LABEL *alabel;
  int length;

  length = SkipWord (string);
  for (alabel = labels; alabel; alabel = alabel->next)
    if (memcmp (string, alabel->name, length) == 0)
      break;
  if (alabel)
    return alabel;
  else
    {
      DisplayWarningError (ERROR_MESSAGE, _("label \"%s\" not found"), string,
			   length);

      return NULL;
    }
}

/* clear all the labels and variable
 *
 * clear the lists, set labels and vars to NULL */
static void
ClearLabelVar (void)		/* free labels and vars */
{
  LABEL *alabel;
  VARIABLE *avar;

  while (labels != NULL)
    {
      alabel = labels;
      labels = labels->next;
      if (alabel->name != NULL)
	free (alabel->name);
      free (alabel);
    }
  while (vars != NULL)
    {
      avar = vars;
      vars = vars->next;
      free (avar);
    }
}

/* add a variable to the top of the variable list
 *
 * parameter:
 *   i - the line no of source program
 * return:
 *   sentences[i].length
 */
static int
AddVar (int i)
{
  VARIABLE *avar;

  /* add a variable at the head of vars */
  if (sentences[i].label)	/* have var in this line */
    {
      if ((avar = (VARIABLE *) malloc (sizeof (VARIABLE))) != NULL)
	{
	  avar->name = sentences[i].label->name;
	  avar->address = sentences[i].label->address;
	  avar->length = sentences[i].length;

	  /* variable type */
	  if (isdigit (*sentences[i].operand))	/* dec number */
	    avar->type = VAR_DEC;
	  else if (*sentences[i].operand == '#')	/* hex number */
	    avar->type = VAR_HEX;
	  else if (isupper (*sentences[i].operand))	/* label */
	    avar->type = VAR_LABEL;
	  else if (*sentences[i].operand == '\'')	/* string */
	    avar->type = VAR_STRING;

	  avar->next = vars;
	  vars = avar;
	}
      else			/* show error */
	{
	  display (_("No enough memory\n"));
	}
    }

  return sentences[i].length;
}

/* Pseudo Operation */

/* get the length of pseudo DC
 *
 * parameter:
 *   operand - the begin of operand
 * return:
 *   the length of pseudo DC
 */
static int
length_of_DC (char *operand)
{
  int size = 0;

  if (isdigit (*operand) || isupper (*operand) || *operand == '#')	/* number, label or hex number */
    size = 1;
  else if (*operand == '\'')	/* string */
    for (size = 0, operand++; *operand != '\''; operand++)
      size++;

  return size;
}

/* get the length of pseudo DS
 *
 * parameter:
 *   operand - the begin of operand
 * return:
 *   the length of pseudo DS
 */
static int
length_of_DS (char *operand)
{
  int size = 0;

  /* change string of dec number to number */
  for (; *operand && isdigit (*operand); operand++)
    size = size * 10 + (*operand) - '0';

  return size;
}

/* assemble pseudo DC, add a variable if have
 *
 * parameter:
 *   address - the start address of DC
 *   i - the line number of source
 * return:
 *   the length of DC
 */
static int
DC (UWORD address, int i)
{
  int value;
  char *operand;

  operand = sentences[i].operand;
  if (isdigit (*operand))	/* dec number */
    {
      sscanf (operand, "%u", &value);
      memoryWrite (address, value);
    }
  else if (*operand == '#')	/* hex number */
    {
      operand++;
      sscanf (operand, "%X", &value);
      memoryWrite (address, value);
    }
  else if (isupper (*operand))	/* label */
    {
      LABEL *alabel;

      if ((alabel = FindLabel (operand)) != NULL)	/* found the label */
	memoryWrite (address, alabel->address);
    }
  else if (*operand == '\'')	/* string */
    {
      operand++;
      while (*operand != '\'')
	memoryWrite (address++, *operand++);
    }
  else
    DisplayWarningError (WARNING_MESSAGE, _("illegal constant"));

  return AddVar (i);		/* if have var in this line, add a var at the head of vars */
}

/* assemble pseudo DS, add a variable if have
 *
 * parameter:
 *   address - the start address of DS
 *   i - the line number of source
 * return:
 *   the length of DS
 */
static int
DS (UWORD address, int i)
{
  return AddVar (i);		/* if have var in this line, add a var at the head of vars */
}

static int
length_of_START (char *operand)
{
  return 0;
}

static int
length_of_END (char *operand)
{
  return 0;
}

static int
START (UWORD address, int i)
{
  LABEL *alabel;

  if (isupper (*sentences[i].operand))
    {
      if ((alabel = FindLabel (sentences[i].operand)) != NULL)
	initreg.PC = alabel->address;
      else
	{
	  initreg.PC = address;
	  DisplayWarningError (WARNING_MESSAGE, _("label \"%s\" no found"),
			       sentences[i].operand);
	}
    }
  else
    initreg.PC = address;

  if (address != 0)
    DisplayWarningError (WARNING_MESSAGE,
			 _("START is not at the begin of program"));

  return 0;
}

static int
END (UWORD address, int i)
{
  end_PC = address;

  return 0;
}

/* Pseudo Operation Table */
POT pot[] = {
  {"DC", length_of_DC, DC}
  , {"DS", length_of_DS, DS}
  , {"START", length_of_START, START}
  , {"END", length_of_END, END}
};

/* get length of the instruction
 *
 * parameter:
 *   string - the begin of a instruction without label
 *   i - the line number of source program
 * return:
 *   length of the instruction
 * function:
 *   output sentences[i].type
 *                      .length
 *                      .operand
 */
static int
InstructionLength (char *string, int i)
{
  int len, k;

  len = SkipWord (string);	/* length of operation */
  if (string[0] == '\0')
    {
      sentences[i].type = NONE;
      sentences[i].length = 0;
      sentences[i].operand = string;
      return sentences[i].length;
    }
  if (string[0] == ';')
    {
      sentences[i].type = REMARK;
      sentences[i].length = 0;
      sentences[i].operand = string;
      return sentences[i].length;
    }
  for (k = 1; k < MO_NUM; k++)
    if (memcmp (string, mot[k].name, len) == 0 && strlen (mot[k].name) == len)
      {
	sentences[i].type = MACHINE;
	sentences[i].op = k;
	sentences[i].length = mot[k].length;
	string += len;
	sentences[i].operand = string + SkipBlank (string);
	return sentences[i].length;
      }
  for (k = 0; k < PO_NUM; k++)
    if (memcmp (string, pot[k].name, len) == 0)
      {
	sentences[i].type = PSEUDO;
	sentences[i].op = k;
	string += len;		/* skip operation name */
	sentences[i].operand = string + SkipBlank (string);	/* skip blank between operation and operand */
	sentences[i].length = (*pot[k].length) (sentences[i].operand);

	return sentences[i].length;
      }

  DisplayWarningError (ERROR_MESSAGE, _("instruction \"%s\" illegal"), string,
		       len);

  return 0;
}

/* get the number of GR register
 *
 * parameter:
 *   string - the begin of string: GR0, GR1, GR2, GR3 or GR4
 * return:
 *   the number of GR, equal 0, 1, 2, 3 or 4
 */
static int
GetGRno (const char *string)
{
  int i;

  for (i = 0; i <= 4; i++)
    if (memcmp (string, strGR[i], 3) == 0)
      break;
  if (i <= 4)
    return i;
  /* Show error illegal GR */
  return -1;
}

/* fill E in code
 *
 * parameter:
 *   string - the string of E. include "ADR,GRx" or "ADR"
 *   code - to fill in
 *          fill ADR & XRno
 */
static void
FillE (char *string, CODE * code)
{
  /* get ADR */
  code->ADR = 0;
  if (isdigit (string[0]))	/* is number address */
    while (isdigit (*string))
      code->ADR = code->ADR * 10 + (*string++) - '0';
  else if (string[0] == '-')	/* negative */
    {
      string++;
      while (isdigit (*string))
	code->ADR = code->ADR * 10 + (*string++) - '0';
      code->ADR = -code->ADR;
    }
  else if (isalpha (*string))	/* is label */
    {
      LABEL *alabel;

      if ((alabel = FindLabel (string)) != NULL)
	{
	  code->ADR = alabel->address;
	  string += strlen (alabel->name);
	}
    }

  string += SkipBlank (string);

  /* skip comma. If not exists, it means XR not exists */
  if (*string == ',')
    {
      string++;
      string += SkipBlank (string);
      if ((code->XRno = GetGRno (string)) <= 0)
	{			/* Show error illegal XR */
	  code->XRno = 0;
	  DisplayWarningError (WARNING_MESSAGE, _("GR0 isn't XR"));
	}
    }
  else
    code->XRno = 0;
}

/* first scan the source progarm
 *
 * return the length of COMET code
 *
 */
static UWORD
firstscan (void)
{
  int i, j;
  UWORD address = 0;

  for (i = 0; i < SentenceNum; i++)
    {
      CurrentLine = i;
      j = 0;
      sentences[i].address = address;
      if (isupper (sentences[i].string[j]))	/* label */
	{
	  j += AddLabel (sentences[i].string + j, address);
	  sentences[i].label = labels;
	}
      else
	sentences[i].label = NULL;

      j += SkipBlank (sentences[i].string + j);

      address += InstructionLength (sentences[i].string + j, i);
    }
  return address;
}

/* second scan the source program
 *
 *
 */
static void
secondscan (void)
{
  int i, j;
  UWORD address = 0;
  CODE code;
  LABEL *alabel;
  char *string;

  for (i = 0; i < SentenceNum; i++)
    {
      CurrentLine = i;
      j = 0;
      memset (&code, 0, sizeof (CODE));	/* clear code to zero */
      if (sentences[i].type == MACHINE)
	{
	  switch (mot[sentences[i].op].type)
	    {
	    case GR_E:
	      code.GRno = GetGRno (sentences[i].operand);
	      string = sentences[i].operand + 3;
	      string += SkipComma (string);
	      FillE (string, &code);
	      break;
	    case GR:
	      code.GRno = GetGRno (sentences[i].operand);
	      break;
	    case E:
	      FillE (sentences[i].operand, &code);
	      break;
	    case BLANK:
	      if (*sentences[i].operand && sentences[i].operand[0] != ';')	/* show error operand no blank */
		DisplayWarningError (WARNING_MESSAGE,
				     _("operand should be blank"));
	      break;
	    case ALABEL_NLABEL:
	      for (code.op = 1; code.op < MO_NUM && strcmp (mot[code.op].name, "PUSH") != 0; code.op++);	/* get number of PUSH */

	      /* push ALABEL */
	      if ((alabel = FindLabel (sentences[i].operand)) != NULL)
		code.ADR = alabel->address;
	      else
		code.ADR = 0;
	      CodeWrite (address, &code);	/* Write to memory */
	      address += 2;

	      string = sentences[i].operand + strlen (alabel->name);
	      string += SkipComma (string);

	      /* push NLABEL */
	      if ((alabel = FindLabel (string)) != NULL)	/* push NLABEL */
		code.ADR = alabel->address;
	      else
		code.ADR = 0;
	      CodeWrite (address, &code);	/* Write to memory */
	      address += 2;

	      memset (&code, 0, sizeof (CODE));	/* clear code to zero */
	      break;
	    default:
	      /* Show error wrong operand type */
	      DisplayWarningError (ERROR_MESSAGE, _("wrong operand type.\n"));
	      continue;
	    }
	  code.op = sentences[i].op;	/* fill op */
	  CodeWrite (address, &code);	/* Write the code to memory */
	  address += 2;		/* TODO: no change to address+=sentences[i].length because of IN & OUT */
	}
      else if (sentences[i].type == PSEUDO)
	address += (*pot[sentences[i].op].assemble) (address, i);
    }
}

/* Assemble CASL source program to code
 *
 * parameter
 *   the string of source program which can be freed.
 * return
 *   length of code. If fail, return 0.
 */
int
assemble (char source[])
{
  int i, n = 0;
  int len;			/* the length of COMET codes */
  extern int statusRunning;	/* bool, define in execute.c */

  /* change \n to \0 */
  for (i = 0, n = 0; source[i]; i++)
    if (source[i] == '\n')
      {
	source[i] = '\0';
	n++;
      }
  n++;
  SentenceNum = n;

  if (sentences)		/* TRUE, if have assembled */
    {
      free (sentences[0].string);	/* free former source program */
      free (sentences);		/* free former sentences */
      ClearLabelVar ();
      memoryClear ();
    }
  if ((sentences = (SENTENCE *) malloc (sizeof (SENTENCE) * n)) != NULL)
    {
      memset (sentences, 0, sizeof (SENTENCE) * n);
      /* save source to sentences[].name */
      sentences[0].string = source;
      for (i = 1; i < n; i++)
	sentences[i].string =
	  sentences[i - 1].string + strlen (sentences[i - 1].string) + 1;

      len = firstscan ();
      if (len > MEMORY_SIZE)
	display (_("The size of the binary program is more than 1M.\n"));
      else
	{
	  memset (&initreg, 0, sizeof (initreg));
	  secondscan ();

	  statusRunning = FALSE;

	  if (end_PC != len)
	    DisplayWarningError (WARNING_MESSAGE,
				 _("END is not at the end of program"));

	  display (_("The size of the binary program is %d word(s).\n"), len);
	}
    }
  else
    {
      len = 0;

      display (_("No enough memory\n"));
    }

  statusAssembled = TRUE;

  return len;
}

/* show a sentence of source program
 *
 * parameter
 *   address - the address of a sentence
 * return
 *   the line number of sentence
 */
int
asentence (UWORD address)
{
  int i;

  i = GetSentenceLineNo (address);
  display (_("Line %d:%s\n"), i + 1, sentences[i].string);

  return i;
}

/* get sentence line no 获取行号 */
int
GetSentenceLineNo (UWORD address)
{
  int i;

  for (i = 0;
       i < SentenceNum && (sentences[i].address < address
			   || sentences[i].length == 0); i++);

  return i;
}

/* 含汉字的字符串长度,每个汉字长度为1. */
int
cc_strlen (unsigned char *string)
{
  int i;

  for (i = 0; *string != '\0'; i++)
    if (*string >= 128 && *(string + 1) >= 128)
      string += 2;
    else
      string++;

  return i;
}
